[1차] 비밀지도
문제 설명
네오는 평소 프로도가 비상금을 숨겨놓는 장소를 알려줄 비밀지도를 손에 넣었다. 그런데 이 비밀지도는 숫자로 암호화되어 있어 위치를 확인하기 위해서는 암호를 해독해야 한다. 다행히 지도 암호를 해독할 방법을 적어놓은 메모도 함께 발견했다.
- 지도는 한 변의 길이가 n인 정사각형 배열 형태로, 각 칸은 “공백”(“ “) 또는 “벽”(“#”) 두 종류로 이루어져 있다.
- 전체 지도는 두 장의 지도를 겹쳐서 얻을 수 있다. 각각 “지도 1”과 “지도 2”라고 하자. 지도 1 또는 지도 2 중 어느 하나라도 벽인 부분은 전체 지도에서도 벽이다. 지도 1과 지도 2에서 모두 공백인 부분은 전체 지도에서도 공백이다.
- “지도 1”과 “지도 2”는 각각 정수 배열로 암호화되어 있다.
- 암호화된 배열은 지도의 각 가로줄에서 벽 부분을 1, 공백 부분을 0으로 부호화했을 때 얻어지는 이진수에 해당하는 값의 배열이다.
네오가 프로도의 비상금을 손에 넣을 수 있도록, 비밀지도의 암호를 해독하는 작업을 도와줄 프로그램을 작성하라.
입출력 예제 시각화
| 지도 1 | 지도 2 | 비밀지도 | ||||
|---|---|---|---|---|---|---|
01001(2) |
9 | OR ( | ) | 11110(2) |
30 | = | ##### |
10100(2) |
20 | 00001(2) |
1 | # # # |
||
11100(2) |
28 | 10101(2) |
21 | ### # |
||
10010(2) |
18 | 10001(2) |
17 | # ## |
||
01011(2) |
11 | 11100(2) |
28 | ##### |
||
입력 형식
입력으로 지도의 한 변 크기 n 과 2개의 정수 배열 arr1, arr2가 들어온다.
- 1 ≦ n ≦ 16
arr1,arr2는 길이 n인 정수 배열로 주어진다.- 정수 배열의 각 원소 x를 이진수로 변환했을 때의 길이는 n 이하이다. 즉, 0 ≦ x ≦ 2n - 1을 만족한다.
출력 형식
원래의 비밀지도를 해독하여 ‘#’, 공백으로 구성된 문자열 배열로 출력하라.
입출력 예제
| n | arr1 | arr2 | 출력 |
|---|---|---|---|
| 5 | [9, 20, 28, 18, 11] | [30, 1, 21, 17, 28] | ["#####","# # #", "### #", "# ##", "#####"] |
| 6 | [46, 33, 33, 22, 31, 50] | [27, 56, 19, 14, 14, 10] | ["######", "### #", "## ##", " #### ", " #####", "### # "] |
해설
전형적인 비트마스킹 문제이다. 많은 경우 서버의 채점 환경은 ‘Little Endian’이기 때문에 n - 1번째 비트부터 체크하면서 내려와야 한다. 만약 그렇게 냈는데 안 됐다면 서버가 ‘Big Endian’인 특이 케이스일 수 있으니 0번째 비트부터 체크하면 된다.
격자가 있든 말든 본질은 탐색도 그래프도 아닌 비트마스킹 문제이다. 그냥 냅다 두 벡터를 OR한 새로운 벡터를 만들고, 그냥 for문 돌려서 비트마스킹으로 추출한다.
비트 추출 원리
(bool)((x >> i) & 1)
이것의 뜻은 x를 i만큼 오른쪽으로 밀고, 그것과 1을 AND연산한 후 1비트만큼 잘라내어 불리언으로 쓰겠다는 뜻이다. 반드시 AND 연산하는 둘의 타입이 같아야 일어날지도 모르는 사고를 막을 수 있다.
- 원하는 비트가 x의 첫 1비트에 올 만큼 밀어준다.
- 1과 AND 연산을 한다.
- (bool)을 씌워 1비트만 잘라낸다.
그렇게 했을 때를 uint8_t x = 128일 때 x의 오른쪽에서 8번째 비트를 가져오는 경우로 한번 생각해 보자.
8비트 부호 없는 정수 128은 이렇게 표현 가능하다.
10000000
^
여기가 x의 첫 비트
이 상황에서 비트를 밀어보자.
10000000
^ x >> 0, (bool)((x >> 0) & 1) == false
10000000
^ x >> 1, (bool)((x >> 1) & 1) == false
10000000
^ x >> 2, (bool)((x >> 2) & 1) == false
10000000
^ x >> 3, (bool)((x >> 3) & 1) == false
10000000
^ x >> 4, (bool)((x >> 4) & 1) == false
10000000
^ x >> 5, (bool)((x >> 5) & 1) == false
10000000
^ x >> 6, (bool)((x >> 6) & 1) == false
10000000
^ x >> 7, (bool)((x >> 7) & 1) == true
와 같이 비트가 잘 추출된다.
이 문제는 주어진 N비트만큼 추출하되 오른쪽부터 왼쪽으로 기계어 어순으로 읽은 후 왼쪽부터 오른쪽으로 사람 어순으로 '#'=1, ' '=0으로 가공해서 던져주는 것이다.
풀이
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
string parse_n_bits(int x, int n) {
string s;
for(int i = n - 1; i >= 0; i--) {
if((bool)((x >> i) & 1)) {
s += "#";
} else {
s += " ";
}
}
return s;
}
vector<string> solution(int n, vector<int> arr1, vector<int> arr2) {
vector<string> answer;
vector<int> with_or_gate(arr1);
for(int i = 0; i < with_or_gate.size(); i++) with_or_gate[i] |= arr2[i];
for(int i = 0; i < with_or_gate.size(); i++) {
answer.push_back(parse_n_bits(with_or_gate[i], n));
}
return answer;
}
참고 사항
매우 난이도가 낮기 때문에 방심할 수 있으나, 언제 비트를 잘라내고 언제 OR 연산하는지 유심히 볼 필요가 있다. 당당하게 날림으로 풀었다가 큰일이 날 수도 있으니 주의하고 또 주의하자.